﻿using System;
using System.Data;
using System.ComponentModel;
using System.Drawing;

using System.Collections.Generic;
using System.Linq;
using System.Text;
using Axiom;
using Axiom.Core;
using Axiom.Graphics;
using Axiom.Configuration;
using Axiom.Math;
using Axiom.Overlays;
using Axiom.Input;



//using Axiom.RenderSystems.OpenGL.Win32;

namespace w4nted
{
    class axiom_engine
    {
        #region Variables
        private string m_configFile = "EngineConfig.xml";
        private string m_logFile = "Axiom.log";
        private long m_lastOverlayUpdate = -1000;
        private float m_moveScale = 0, m_rotScale = 0, m_moveSpeed = 100, m_rotateSpeed = 36;
        private Vector2 m_rotateVector = new Vector2(0, 0);
        private Vector3 m_translateVector = new Vector3(0, 0, 0);
        private Root m_root;
        private Camera m_camera;
        private SceneManager m_sceneManager;
        private RenderWindow m_renderWindow;
        private InputReader m_inputReader;
        #endregion Variables

        #region Protected properties
        /// <summary>
        /// Root (Axiom.Core.Root)
        /// </summary>
        protected Root Root
        {
            get
            {
                return m_root;
            }
            set
            {
                m_root = value;
            }
        }

        /// <summary>
        /// Camera (Axiom.Core.Camera)
        /// </summary>
        protected Camera Camera
        {
            get
            {
                return m_camera;
            }
            set
            {
                m_camera = value;
            }
        }

        /// <summary>
        /// SceneManager (Axiom.Core.SceneManager)
        /// </summary>
        protected SceneManager SceneManager
        {
            get
            {
                return m_sceneManager;
            }
            set
            {
                m_sceneManager = value;
            }
        }


        /// <summary>
        /// RenderWindow (Axiom.Graphics.RenderWindow)
        /// </summary>
        protected RenderWindow RenderWindow
        {
            get
            {
                return m_renderWindow;
            }
            set
            {
                m_renderWindow = value;
            }
        }

        /// <summary>
        /// InputReader (Axiom.Input.InputReader)
        /// </summary>
        protected InputReader InputReader
        {
            get
            {
                return m_inputReader;
            }
            set
            {
                m_inputReader = value;
            }
        }
        #endregion Protected fields

        #region Public properties
        /// <summary>
        /// Gets or set the config file name and path
        /// </summary>
        public string ConfigFile
        {
            get
            {
                return m_configFile;
            }
            set
            {
                m_configFile = value;
            }
        }

        /// <summary>
        /// Gets or sets the config file name and path
        /// </summary>
        public string LogFile
        {
            get
            {
                return m_logFile;
            }
            set
            {
                m_logFile = value;
            }
        }
        #endregion

        #region Init methods
        /// <summary>
        /// Starts the example
        /// </summary>
        ///         
        public virtual void Run()
        {
            try
            {
                if (Setup()) m_root.StartRendering();
            }
            catch (System.Reflection.ReflectionTypeLoadException ex)
            {
                // This catches directx missing (or too old) to log :)
                for (int i = 0; i < ex.LoaderExceptions.Length; i++)
                    if (LogManager.Instance != null)
                        LogManager.Instance.Write(ex.LoaderExceptions[i].Message);
            }
            catch (Exception ex)
            {
                if (LogManager.Instance != null)
                    LogManager.Instance.Write(ex.ToString());
            }

            // TODO: Memory cleanup here..
        }

        protected virtual bool Setup()
        {
            m_root = new Root(ConfigFile, LogFile);

            SetupResources();

            // Run config utility, exit program if it returns false
            if (!Configure())
                return false;
            else
                m_renderWindow = Root.Instance.Initialize(true);

            // Initalize input
            m_inputReader = PlatformManager.Instance.CreateInputReader();
            m_inputReader.Initialize(m_renderWindow, true, true, false, true);

            ChooseSceneManager();
            CreateCamera();
            CreateViewports();

            // Set default mipmap level (NB some APIs ignore this)
            //TextureManager.Instance.DefaultNumMipMaps = 5;

            // Create any resource listeners (for loading screens)
            CreateResourceListener();

            // Add some event handlers
            RegisterEventHandlers();

            // Lastly, create the scene
            CreateScene();

            return true;
        }
        /// <summary>
        /// Adds the searchpaths from "EngineConfig.xml" to resources
        /// </summary>
        protected virtual void SetupResources()
        {

           EngineConfig config = new EngineConfig();

            // load the config file
            // relative from the location of debug and releases executables
            config.ReadXml("EngineConfig.xml");

            // interrogate the available resource paths
            foreach (EngineConfig.FilePathRow row in config.FilePath)
            {
                ResourceManager.AddCommonArchive(row.src, row.type);
            }

        }

        /// <summary>
        /// Configures the application
        /// </summary>
        /// <returns>True if successfull, False to exit the application</returns>
        protected virtual bool Configure()
        {
            // Set selected rendersystem
            m_root.RenderSystem = Root.Instance.RenderSystems[0];
            return true;
        }

        /// <summary>
        /// Chooses scene manager (SceneType.Generic)
        /// </summary>
        protected virtual void ChooseSceneManager()
        {
            // Create a generic scene manager
            m_sceneManager = m_root.SceneManagers.GetSceneManager(SceneType.ExteriorClose);
            SceneManager.SetSkyDome(true, "Examples/CloudySky", 5, 8);


        }

        /// <summary>
        /// Creates a camera at 500 in the Z direction that looks at -300 in the Z directionr
        /// </summary>
        protected virtual void CreateCamera()
        {
            m_camera = m_sceneManager.CreateCamera("PlayerCam");
            CreateCamerainit(m_camera, m_sceneManager);
        }

        protected virtual void CreateCamerainit(Camera m_camera, SceneManager m_sceneManager)
        {
            // Create the camera

            // Position it at 500 in the Z direction
            m_camera.Position = new Vector3(0, 0, 500);

            // Look back along -Z
            m_camera.LookAt(new Vector3(0, 0, -300));
            m_camera.Near = 5;
        }

        /// <summary>
        /// Creates a viewport using mCamera
        /// </summary>
        protected virtual void CreateViewports()
        {
            // Create one viewport, entire window
            Viewport vp = m_renderWindow.AddViewport(m_camera);
            vp.BackgroundColor = ColorEx.Black;

            // Alter the camera aspect ratio to match the viewport
            m_camera.AspectRatio = vp.ActualWidth / vp.ActualHeight;
        }

        /// <summary>
        /// Optional override method where you can create resource listeners (e.g. for loading screens)
        /// </summary>
        protected virtual void CreateResourceListener()
        {

        }

        /// <summary>
        /// Registers event handlers and calls InitOverlay()
        /// </summary>
        protected virtual void RegisterEventHandlers()
        {
            m_root.FrameStarted += UpdateInput;
            m_root.FrameStarted += UpdateOverlay;
            m_root.FrameStarted += FrameStarted;
            m_root.FrameEnded += FrameEnded;

            // Create debug overlay
            InitOverlay();
        }

        /// <summary>
        /// Initalizes the debug overlay (fps, etc..)
        /// </summary>
        protected virtual void InitOverlay()
        {

            Overlay o = OverlayManager.Instance.GetByName("Core/DebugOverlay");
            if (o == null)
                throw new Exception("Could not find overlay named 'Core/DebugOverlay'.");
            o.Show();
        }

        /// <summary>
        /// Creates the scene
        /// </summary>
        protected virtual void CreateScene()
        {

        }
        #endregion init

        #region Event handlers
        /// <summary>
        /// This is run before each frame
        /// </summary>
        /// <param name="source"></param>
        /// <param name="e"></param>
        protected virtual void FrameStarted(object source, FrameEventArgs e)
        {

        }

        /// <summary>
        /// This is run after each frame
        /// </summary>
        /// <param name="source"></param>
        /// <param name="e"></param>
        protected virtual void FrameEnded(object source, FrameEventArgs e)
        {

        }

        /// <summary>
        /// Checks for input and handles it
        /// </summary>
        /// <param name="source"></param>
        /// <param name="e"></param>
        protected virtual void UpdateInput(object source, FrameEventArgs e)
        {
            m_inputReader.Capture();

            Input_handler(source, e, m_inputReader, m_sceneManager, m_camera);

            // TODO: what about window-closing-event?
            if (m_inputReader.IsKeyPressed(KeyCodes.Escape))
            {
                Root.Instance.QueueEndRendering();

                // TODO: Find a better way
                if (m_root != null)
                {
                    // remove event handlers
                    //  engine.FrameStarted -= new FrameEvent( OnFrameStarted );
                    // engine.FrameEnded -= new FrameEvent( OnFrameEnded );

                    m_root.Dispose();
                }

                m_sceneManager.RemoveAllCameras();
                m_sceneManager.RemoveCamera(m_camera);
                m_camera = null;
                Root.Instance.RenderSystem.DetachRenderTarget(m_renderWindow);
                m_renderWindow.Dispose();
                return;
            }

        }

        /// <summary>
        /// Routine to Control Input, override to use 
        /// </summary>
        /// <param name="source"></param>
        /// <param name="e"></param>
        /// <param name="m_inputReader"></param>
        /// <param name="m_sceneManager"></param>
        /// <param name="m_camera"></param>
        protected virtual void Input_handler(object source, FrameEventArgs e, InputReader m_inputReader, SceneManager m_sceneManager, Camera m_camera)
        {
            #region Camera movement
            // Reset vectors
            m_rotateVector.x = m_translateVector.x = 0;
            m_rotateVector.y = m_translateVector.y = 0;
            m_translateVector.z = 0;

            // Move
            m_moveScale = m_moveSpeed * e.TimeSinceLastFrame;

            // Rotate
            m_rotScale = m_rotateSpeed * e.TimeSinceLastFrame;

            // Move forward and back
            if (m_inputReader.IsKeyPressed(KeyCodes.W) || m_inputReader.IsKeyPressed(KeyCodes.Up))
                m_translateVector.z = -m_moveScale;
            else if (m_inputReader.IsKeyPressed(KeyCodes.S) || m_inputReader.IsKeyPressed(KeyCodes.Down))
                m_translateVector.z = m_moveScale;

            // Move left and right
            if (m_inputReader.IsKeyPressed(KeyCodes.A))
                m_translateVector.x = -m_moveScale;
            else if (m_inputReader.IsKeyPressed(KeyCodes.D))
                m_translateVector.x = m_moveScale;

            // Move up and down
            if (m_inputReader.IsKeyPressed(KeyCodes.PageUp))
                m_translateVector.y = m_moveScale;
            else if (m_inputReader.IsKeyPressed(KeyCodes.PageDown))
                m_translateVector.y = -m_moveScale;

            // Rotate left and right
            if (m_inputReader.IsKeyPressed(KeyCodes.Left))
                m_rotateVector.x = -m_rotScale;
            else if (m_inputReader.IsKeyPressed(KeyCodes.Right))
                m_rotateVector.x = m_rotScale;

            // Right mouse button pressed
            if (m_inputReader.IsMousePressed(MouseButtons.Right))
            {
                // Translate
                m_translateVector.x += m_inputReader.RelativeMouseX * 0.13f;
                m_translateVector.y -= m_inputReader.RelativeMouseY * 0.13f;
            }
            else
            {
                // Apply mouse rotation
                m_rotateVector.x += m_inputReader.RelativeMouseX * 0.13f;
                m_rotateVector.y += m_inputReader.RelativeMouseY * 0.13f;
            }

            // Apply changes
            m_camera.Yaw(-m_rotateVector.x);
            m_camera.Pitch(-m_rotateVector.y);
            m_camera.MoveRelative(m_translateVector);
            #endregion Camera movement
        }

        /// <summary>
        /// Updates the debug overlay
        /// </summary>
        protected virtual void UpdateOverlay(object source, FrameEventArgs e)
        {
            if (Root.Instance.Timer.Milliseconds - m_lastOverlayUpdate >= 1000)
            {
                m_lastOverlayUpdate = Root.Instance.Timer.Milliseconds;

                OverlayElement element =
                        OverlayElementManager.Instance.GetElement("Core/DebugText");
                //element.Text = m_renderWindow.DebugText;

                element = OverlayElementManager.Instance.GetElement("Core/CurrFps");
                element.Text = string.Format("Current FPS: {0}", Root.Instance.CurrentFPS);

                element = OverlayElementManager.Instance.GetElement("Core/BestFps");
                element.Text = string.Format("Best FPS: {0}", Root.Instance.BestFPS);

                element = OverlayElementManager.Instance.GetElement("Core/WorstFps");
                element.Text = string.Format("Worst FPS: {0}", Root.Instance.WorstFPS);

                element = OverlayElementManager.Instance.GetElement("Core/AverageFps");
                element.Text = string.Format("Average FPS: {0}", Root.Instance.AverageFPS);

                element = OverlayElementManager.Instance.GetElement("Core/NumTris");
                element.Text = string.Format("Triangle Count: {0}", m_sceneManager.TargetRenderSystem.FacesRendered);
            }
        }
        #endregion Events
    }
}
